package com.unciv.ui.mapeditor

import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.MapType
import com.unciv.logic.map.TileMap
import com.unciv.logic.map.mapgenerator.MapGenerator
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.translations.tr
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.newgamescreen.MapParametersTable
import com.unciv.ui.popup.Popup
import com.unciv.ui.popup.ToastPopup
import com.unciv.ui.utils.*
import kotlin.concurrent.thread

class MapEditorGenerateTab(
    private val editorScreen: MapEditorScreen
): TabbedPager(capacity = 2) {
    private val newTab = MapEditorNewMapTab(this)
    private val partialTab = MapEditorGenerateStepsTab(this)

    // Since we allow generation components to be run repeatedly, it might surprise the user that
    // the outcome stays the same when repeated - due to them operating on the same seed.
    // So we change the seed behind the scenes if already used for a certain step...
    private val seedUsedForStep = mutableSetOf<MapGeneratorSteps>()

    init {
        name = "Generate"
        top()
        addPage("New map", newTab,
            ImageGetter.getImage("OtherIcons/New"), 20f,
            shortcutKey = KeyCharAndCode.ctrl('n'))
        addPage("Partial", partialTab,
            ImageGetter.getImage("OtherIcons/Settings"), 20f,
            shortcutKey = KeyCharAndCode.ctrl('g'))
        selectPage(0)
        setButtonsEnabled(true)
        partialTab.generateButton.disable()  // Starts with choice "None"
    }

    private fun setButtonsEnabled(enable: Boolean) {
        newTab.generateButton.isEnabled = enable
        newTab.generateButton.setText( (if(enable) "Create" else "Working...").tr())
        partialTab.generateButton.isEnabled = enable
        partialTab.generateButton.setText( (if(enable) "Generate" else "Working...").tr())
    }

    private fun generate(step: MapGeneratorSteps) {
        if (step <= MapGeneratorSteps.Landmass && step in seedUsedForStep) {
            // reseed visibly when starting from scratch (new seed shows in advanced settings widget)
            newTab.mapParametersTable.reseed()
            seedUsedForStep -= step
        }
        val mapParameters = editorScreen.newMapParameters.clone()  // this clone is very important here
        val message = mapParameters.mapSize.fixUndesiredSizes(mapParameters.worldWrap)
        if (message != null) {
            Gdx.app.postRunnable {
                ToastPopup( message, editorScreen, 4000 )
                newTab.mapParametersTable.run { mapParameters.mapSize.also {
                    customMapSizeRadius.text = it.radius.toString()
                    customMapWidth.text = it.width.toString()
                    customMapHeight.text = it.height.toString()
                } }
            }
            return
        }

        if (step == MapGeneratorSteps.Landmass && mapParameters.type == MapType.empty) {
            ToastPopup("Please don't use step 'Landmass' with map type 'Empty', create a new empty map instead.", editorScreen)
            return
        }

        if (step in seedUsedForStep) {
            mapParameters.reseed()
        } else {
            seedUsedForStep += step
        }

        Gdx.input.inputProcessor = null // remove input processing - nothing will be clicked!
        setButtonsEnabled(false)

        fun freshMapCompleted(generatedMap: TileMap, mapParameters: MapParameters, newRuleset: Ruleset, selectPage: Int) {
            MapEditorScreen.saveDefaultParameters(mapParameters)
            editorScreen.loadMap(generatedMap, newRuleset, selectPage) // also reactivates inputProcessor
            editorScreen.isDirty = true
            setButtonsEnabled(true)
        }
        fun stepCompleted(step: MapGeneratorSteps) {
            if (step == MapGeneratorSteps.NaturalWonders) editorScreen.naturalWondersNeedRefresh = true
            editorScreen.mapHolder.updateTileGroups()
            editorScreen.isDirty = true
            setButtonsEnabled(true)
            Gdx.input.inputProcessor = editorScreen.stage
        }

        // Map generation can take a while and we don't want ANRs
        thread(name = "MapGenerator", isDaemon = true) {
            try {
                val (newRuleset, generator) = if (step > MapGeneratorSteps.Landmass) null to null
                    else {
                        val newRuleset = RulesetCache.getComplexRuleset(mapParameters)
                        newRuleset to MapGenerator(newRuleset)
                    }
                when (step) {
                    MapGeneratorSteps.All -> {
                        val generatedMap = generator!!.generateMap(mapParameters)
                        Gdx.app.postRunnable {
                            freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 0)
                        }
                    }
                    MapGeneratorSteps.Landmass -> {
                        // This step _could_ run on an existing tileMap, but that opens a loophole where you get hills on water - fixing that is more expensive than always recreating 
                        mapParameters.type = MapType.empty
                        val generatedMap = generator!!.generateMap(mapParameters)
                        mapParameters.type = editorScreen.newMapParameters.type
                        generator.generateSingleStep(generatedMap, step)
                        val savedScale = editorScreen.mapHolder.scaleX
                        Gdx.app.postRunnable {
                            freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 1)
                            editorScreen.mapHolder.zoom(savedScale)
                        }
                    }
                    else -> {
                        editorScreen.tileMap.mapParameters.seed = mapParameters.seed
                        MapGenerator(editorScreen.ruleset).generateSingleStep(editorScreen.tileMap, step)
                        Gdx.app.postRunnable {
                            stepCompleted(step)
                        }
                    }
                }
            } catch (exception: Exception) {
                println("Map generator exception: ${exception.message}")
                Gdx.app.postRunnable {
                    setButtonsEnabled(true)
                    Gdx.input.inputProcessor = editorScreen.stage
                    Popup(editorScreen).apply {
                        addGoodSizedLabel("It looks like we can't make a map with the parameters you requested!".tr())
                        row()
                        addCloseButton()
                    }.open()
                }
            }
        }
    }

    class MapEditorNewMapTab(
        private val parent: MapEditorGenerateTab
    ): Table(BaseScreen.skin) {
        val generateButton = "".toTextButton()
        val mapParametersTable = MapParametersTable(parent.editorScreen.newMapParameters, isEmptyMapAllowed = true)

        init {
            top()
            pad(10f)
            add("Map Options".toLabel(fontSize = 24)).row()
            add(mapParametersTable).row()
            add(generateButton).padTop(15f).row()
            generateButton.onClick { parent.generate(MapGeneratorSteps.All) }
        }
    }

    class MapEditorGenerateStepsTab(
        private val parent: MapEditorGenerateTab
    ): Table(BaseScreen.skin) {
        private val optionGroup = ButtonGroup<CheckBox>()
        val generateButton = "".toTextButton()
        private var choice = MapGeneratorSteps.None
        private val newMapParameters = parent.editorScreen.newMapParameters
        private val tileMap = parent.editorScreen.tileMap
        private val actualMapParameters = tileMap.mapParameters

        init {
            top()
            pad(10f)
            defaults().pad(2.5f)
            add("Generator steps".toLabel(fontSize = 24)).row()
            optionGroup.setMinCheckCount(0)
            for (option in MapGeneratorSteps.values()) {
                if (option <= MapGeneratorSteps.All) continue
                val checkBox = option.label.toCheckBox {
                        choice = option
                        generateButton.enable()
                    }
                add(checkBox).row()
                optionGroup.add(checkBox)
            }
            add(generateButton).padTop(15f).row()
            generateButton.onClick {
                parent.generate(choice)
                choice.copyParameters?.invoke(newMapParameters, actualMapParameters)
            }
        }
    }
}
